﻿using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Threading;
using SoftwareConsulting.BI11.Studio;
using SoftwareConsulting.BI11.Studio.BasicEntities;
using SoftwareConsulting.Interfaces.BasicEntities;
using SoftwareConsulting.BI11.Studio.InternalServices;
using SoftwareConsulting.BI11.Studio.Core.DBProviders.Properties;

namespace SoftwareConsulting.BI11.Studio.Core.DBProviders
{
    /// <summary>
    /// Абстрактный класс работы с БД
    /// </summary>
    public abstract class DBProvider
    {
        private DataSet _dset;
        private bool _locked;

        /// <summary>
        /// Возвращает префикс отношений
        /// </summary>
        protected static string RelationPrefix
        {
            get
            {
                return Resources.BI_DATABASE_RELATION_PREFIX;
            }
        }

        /// <summary>
        /// Возвращает/устанавливает ссылку на датасет
        /// </summary>
        /// <remarks>нужно для дочерних классов</remarks>
        protected DataSet DataSet
        {
            get
            {
                return _dset;
            }
            set
            {
                _dset = value;
            }
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        protected DBProvider()
        {            
            _locked = false;            
        }               

        /// <summary>
        /// Перезагружает информацию о генеральных таблицах
        /// </summary>
        protected abstract void ResetInfoAboutGeneralTables();        

        /// <summary>
        /// Возвращает/устанавливает флаг блокировки БД от изменений
        /// </summary>
        public bool Locked
        {
            get
            {
                return _locked;
            }
            set
            {
                _locked = value;
            }
        }

        /// <summary>
        /// Возвращает читателя таблицы
        /// </summary>
        /// <param name="needTable"></param>
        /// <returns></returns>
        protected virtual DataTableReader GetTableReader(object needTable)
        {
            DataTable dt = GetTable(needTable);
            Assert.FailIf(dt == null, Assert.Id.Assert00182);
            return dt.CreateDataReader();
        }

        /// <summary>
        /// Возвращает системную таблицу
        /// </summary>
        /// <param name="needTable"></param>
        /// <returns></returns>
        protected abstract DataTable GetTable(object needTable);

        protected abstract string GetNamespaceOfZone(object tableZone);

        /*оказалось пока не нужноpublic abstract object GetZoneByNamespace(string namespaceName);*/
        
        /// <summary>
        /// Возвращает таблицу
        /// </summary>
        /// <param name="tableZone"></param>
        /// <param name="tableName"></param>
        /// <returns></returns>
        private DataTable GetTable(object tableZone, string tableName)
        {
            DataTable dt = GetTable(tableZone);
            if (dt.TableName != tableName)
            {
                return _dset.Tables[tableName, GetNamespaceOfZone(tableZone)];
            }
            return dt;
        }

        /// <summary>
        /// Сохраняет БД по указанному адресу
        /// </summary>
        /// <param name="path"></param>
        public void Save(string path)
        {            
            _dset.WriteXml(path, XmlWriteMode.WriteSchema);
        }        

        /// <summary>
        /// Загружает БД из указанного адреса
        /// </summary>
        /// <param name="path"></param>
        /// <returns>true - успешно, false - неуспешно</returns>
        public bool Load(string path)
        {
            if (!Locked)
            {                
                if (!File.Exists(path))
                {
                    return false;
                }
                _dset.Reset();
                _dset.ReadXmlSchema(path);
                _dset.ReadXml(path, XmlReadMode.IgnoreSchema);
                ResetInfoAboutGeneralTables();
            }
            return false;
        }

        /// <summary>
        /// Удаляет все строки из таблицы
        /// </summary>        
        public virtual bool GeneralClearTable(object needTable)
        {
            if (!Locked)
            {
                GetTable(needTable).Clear();
                return true;
            }
            return false;
        }

        /// <summary>
        /// Добавляет строку в конец таблицы
        /// </summary>        
        /// <paramname="values">значения в новой строке</param>        
        public virtual bool GeneralAddRow(object needTable, INamedList newValues)
        {
            if (!Locked)
            {     
                DataTable dt = GetTable(needTable);
                object[] objs = new object[dt.Columns.Count];
                foreach(KeyValuePair<string,object> entry in newValues)
                {                                        
                    objs[dt.Columns[entry.Key].Ordinal] = entry.Value;
                }                
                dt.Rows.Add(objs);
                return true;
            }
            return false;
        }
        /// <summary>
        /// Возвращает число строк в таблице
        /// </summary>
        public int GeneralRowsCount(object needTable)
        {
            DataTable dt = GetTable(needTable);            
            return dt.Rows.Count;
        }

        /// <summary>
        /// Удаляет строки из таблицы по известным полям
        /// </summary>        
        /// <param name="values"></param>
        public virtual bool GeneralDeleteRowsByKnownFields(object needTable, INamedList needFieldValues)
        {
            if (!Locked)
            {                       
                DataTable dt = GetTable(needTable);                
                List<int> indicies = GeneralGetRowIndiciesByKnownFields(needTable, needFieldValues, true);
                int c = indicies.Count;
                for (int i = 0; i < c; i++)
                {
                    dt.Rows.RemoveAt(indicies[i]);
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// Заменяет строки в таблице
        /// </summary>                
        public virtual bool GeneralChangeRowValues(object needTable, INamedList findValues, INamedList setValues, bool findAll)
        {                        
            if (!Locked)
            {
                DataTable dt = GetTable(needTable);
                List<int> indicies = GeneralGetRowIndiciesByKnownFields(needTable, findValues, findAll);
                int rc = indicies.Count;
                for (int ri = 0; ri < rc; ri++)
                {                    
                    object[] objs = dt.Rows[indicies[ri]].ItemArray;
                    foreach(KeyValuePair<string,object> entry in setValues)
                    {
                        objs[dt.Columns[entry.Key].Ordinal] = entry.Value;
                    }                    
                    dt.Rows[indicies[ri]].ItemArray = objs;
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// Возвращает индексы строк по значению известных полей
        /// </summary>
        /// <param name="needTable"></param>        
        /// <returns></returns>
        private List<int> GeneralGetRowIndiciesByKnownFields(object needTable, INamedList knownFields, bool findAll)
        {
            DataTable dt = GetTable(needTable);
            DataTableReader reader = GetTableReader(needTable);
            List<int> res = new List<int>();
            int i = -1;
            while (reader.Read())
            {
                i++;
                object[] haveValues = new object[reader.FieldCount];
                reader.GetValues(haveValues);                
                bool wrongrow = false;
                foreach (KeyValuePair<string, object> entry in knownFields)
                {
                    if (!(GeneralMethods.Identical(haveValues[dt.Columns[entry.Key].Ordinal], entry.Value)))
                    {
                        wrongrow = true;
                        break;
                    }
                }                
                if (!wrongrow)
                {
                    res.Add(i);
                    if (!findAll)
                    {
                        break;
                    }
                }               
            }
            return res;
        }

        /// <summary>
        /// Возвращает значения всех полей строки по ее индексу
        /// </summary>
        /// <param name="needTable"></param>
        /// <param name="knownField"></param>
        /// <returns></returns>
        public INamedList GeneralGetRowValuesByIndex(object needTable, int rowIndex)
        {
            DataTable dt = GetTable(needTable);
            INamedList res = new NamedList();
            object[] haveValues = dt.Rows[rowIndex].ItemArray;
            int c = haveValues.Length;
            for (int i = 0; i < c; i++)
            {
                res.AddValue(dt.Columns[i].ColumnName,haveValues[i]);                
            }
            return res;
        }

        /// <summary>
        /// Возвращает значения всех полей первой встретившейся строки, у которой в нужном поле указано искомое значение
        /// </summary>
        /// <param name="needTable"></param>
        /// <param name="knownField"></param>
        /// <returns></returns>
        public INamedList GeneralGetFirstRowValuesByKnownFields(object needTable, INamedList knownFields)
        {
            INamedList res = new NamedList();
            DataTable dt = GetTable(needTable);
            List<int> indicies = GeneralGetRowIndiciesByKnownFields(needTable, knownFields, false);
            if (indicies.Count == 0)
                return res;
            object[] haveValues = dt.Rows[indicies[0]].ItemArray;            
            int c = haveValues.Length;
            for (int i = 0; i < c; i++)
            {
                res.AddValue(dt.Columns[i].ColumnName, haveValues[i]);                
            }                    
            return res;
        }        
       
        #region работа с любыми таблицами
        /// <summary>
        /// Собственно создание отношений между двумя таблицами
        /// </summary>
        /// <param name="parentTable"></param>
        /// <param name="parentColName"></param>
        /// <param name="childTable"></param>
        /// <param name="childColName"></param>
        /// <returns></returns>
        protected virtual string CustomBindTablesNow(DataTable parentTable, string parentColName, DataTable childTable, string childColName)
        {                        
            string relname =
                RelationPrefix +
                "_" +
                parentTable.TableName +
                "_" +
                parentColName +
                "_" +
                childTable.TableName +
                "_" +
                childColName;
            if (_dset.Relations.IndexOf(relname) == -1)
            {                                   
                _dset.Relations.Add(
                    relname,
                    parentTable.Columns[parentColName],
                    childTable.Columns[childColName],
                    parentTable.Columns[parentColName].Unique);                   
            }
            else
            {
                //такое отношение уже есть;
                //игнорим
            }                                        
            return relname;
        }

        /// <summary>
        /// Создает отношение между двумя таблицами
        /// </summary>
        /// <param name="parentTable"></param>
        /// <param name="parentColName"></param>
        /// <param name="childTable"></param>
        /// <param name="childColName"></param>
        /// <returns>название отношения</returns>
        public string CustomBindTables(object parentZone, string parentTableName, string parentColName, object childZone, string childTableName, string childColName)
        {
            if (!Locked)
            {                
                DataTable parentTable = GetTable(parentZone, parentTableName);                
                DataTable childTable = GetTable(childZone, childTableName);                
                return CustomBindTablesNow(parentTable, parentColName, childTable, childColName);
            }
            return string.Empty;
        }

        /// <summary>
        /// Обновляет имя произвольной таблицы, а если такой еще нет - то создает с новым именем
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="newName"></param>
        /// <returns>true - обновили имеющуюся таблицу, false - создали новую</returns>
        public virtual bool CustomUpdateTable(string tableName, string newName, object tableZone)
        {
            if (!Locked)
            {                
                DataTable dt = GetTable(tableZone, tableName);
                if (dt == null)
                {
                    _dset.Tables.Add(newName, GetNamespaceOfZone(tableZone));                    
                    return false;
                }
                dt.TableName = newName;                
                return true;
            }
            return false;
        }

        /// <summary>
        /// Собственно обновление свойств колонки
        /// </summary>
        /// <param name="col"></param>
        /// <param name="props"></param>
        /// <param name="toSetDefValue"></param>
        /// <remarks>если указанного поля не существует - то создает его</remarks>
        protected virtual void SetColumnPropertiesNow(DataTable dt, DataColumn col, DataColumnProperties props, bool toSetDefValue)
        {
            //string oldColName = col == null ? string.Empty : col.ColumnName;
            string newColName = props.Name;
            bool toAdd = false;
            if (col == null)
            {
                col = new DataColumn(newColName, props.ColumnType);
                toAdd = true;
            }
            else
            {
                col.Caption = newColName;
                col.ColumnName = newColName;
                col.DataType = props.ColumnType;
            }
            if (toSetDefValue)
            {
                col.DefaultValue = props.DefaultValue;
            }
            else
            {
                if (!(col.DefaultValue is DBNull))
                {
                    col.DefaultValue = null;
                }
            }
            //пока убираю чтобы понять что за эксепшены здесь надо отлавливать 26.07.2010 try
            //пока убираю чтобы понять что за эксепшены здесь надо отлавливать 26.07.2010 {
                col.Unique = props.IsUniqueValue;
            //пока убираю чтобы понять что за эксепшены здесь надо отлавливать 26.07.2010 }
            //пока убираю чтобы понять что за эксепшены здесь надо отлавливать 26.07.2010 catch
            //пока убираю чтобы понять что за эксепшены здесь надо отлавливать 26.07.2010 {
            //пока убираю чтобы понять что за эксепшены здесь надо отлавливать 26.07.2010     //у роби была эта глушилка
            //пока убираю чтобы понять что за эксепшены здесь надо отлавливать 26.07.2010 }
            col.AutoIncrement = props.ToAutoInc;
            if (props.ToAutoInc)
            {
                col.AutoIncrementSeed = props.AutoIncSeed;
                col.AutoIncrementStep = props.AutoIncStep;
            }
            if (toAdd)
            {
                //пока убираю чтобы понять что за эксепшены здесь надо отлавливать 26.07.2010 try
                //пока убираю чтобы понять что за эксепшены здесь надо отлавливать 26.07.2010 {
                    dt.Columns.Add(col);
                //пока убираю чтобы понять что за эксепшены здесь надо отлавливать 26.07.2010 }
                //пока убираю чтобы понять что за эксепшены здесь надо отлавливать 26.07.2010 catch
                //пока убираю чтобы понять что за эксепшены здесь надо отлавливать 26.07.2010 {
                //пока убираю чтобы понять что за эксепшены здесь надо отлавливать 26.07.2010     //у роби была эта глушилка
                //пока убираю чтобы понять что за эксепшены здесь надо отлавливать 26.07.2010 }
            }            
        }

        /// <summary>
        /// Изменяет свойства поля произвольной таблицы
        /// </summary>
        /// <param name="tableName"></param>
        /// <remarks>если указанного поля не существует - то создает его</remarks>
        public virtual void CustomSetColumnProperties(object tableZone, string tableName, string columnName, DataColumnProperties properties, bool toSetDefValue)
        {
            if (!Locked)
            {                
                DataTable dt = GetTable(tableZone, tableName);                
                DataColumnCollection cols = dt.Columns;
                int cindex = cols.IndexOf(columnName);
                DataColumn col = null;
                if (cindex != -1)
                {
                    col = cols[cindex];
                }
                SetColumnPropertiesNow(dt, col, properties, toSetDefValue);             
            }            
        }

        /// <summary>
        /// Удаляет все строки из произвольной таблицы
        /// </summary>        
        public virtual bool CustomClearTable(object tableZone, string tableName)
        {
            if (!Locked)
            {
                DataTable dt = GetTable(tableZone, tableName);
                dt.Clear();
                return true;
            }
            return false;
        }

        /// <summary>
        /// Проверяет, существует ли заданная таблица
        /// </summary>
        /// <param name="tableZone"></param>
        /// <param name="tableName"></param>
        /// <returns>true - существует, false - не существует</returns>
        private bool CustomIsTableExists(object tableZone, string tableName)
        {
            DataTable dt = GetTable(tableZone);
            if ((dt != null) && (dt.TableName == tableName))
                return true;
            if (_dset.Tables.Contains(tableName, GetNamespaceOfZone(tableZone)))
                return true;
            return false;
        }

        /// <summary>
        /// Клонирует таблицу
        /// </summary>
        /// <param name="fromTableZone"></param>
        /// <param name="fromTableName"></param>
        /// <param name="toTableZone"></param>
        /// <param name="toTableName"></param>
        /// <remarks>без копирования отношений</remarks>
        public void CustomCloneTable(object fromTableZone, string fromTableName, object toTableZone, string toTableName)
        {            
            if (CustomIsTableExists(toTableZone, toTableName))
                throw new BITableExistsException(null);
            DataTable fromTable = GetTable(fromTableZone, fromTableName);
            ////////
            DataTable toTable = fromTable.Copy();
            toTable.Namespace = GetNamespaceOfZone(toTableZone);
            toTable.TableName = toTableName;
            _dset.Tables.Add(toTable);
            /*DataTable toTable = GetTable(toTableZone, toTableName);
            CustomUpdateTable(toTableName, toTableName, toTableZone);            
            //TODO клонировать поля и записи таблицы*/
        }

        /// <summary>
        /// Вытягивает значения в указанную таблицу по отношениям
        /// </summary>
        public virtual bool CustomPullValuesByRelations(object toTableZone, string toTableName, INamedList relationsNames)
        {
            if (!Locked)
            {
                DataTable toTable = GetTable(toTableZone, toTableName);
                //нафиг не надо, т.к. удаление и обновление сработают по отношениям, а вот добавлю новые строки я дальше вручнуюCustomClearTable(toTableZone, toTableName, false);
                foreach (KeyValuePair<string, object> entry in relationsNames)
                {
                    string toTableColName = entry.Key;
                    DataColumn toTableCol = toTable.Columns[toTableColName];                    
                    string relName = entry.Value as string;
                    DataRelation rel = _dset.Relations[relName];
                    DataTable fromTable = rel.ParentTable;
                    DataColumn fromTableCol = rel.ParentColumns[0];                    
                    int cc = fromTable.Rows.Count;                    
                    for (int j = 0; j < cc; j++)
                    {
                        object putValue = fromTable.Rows[j][fromTableCol];
                        DataRow toTableRow = null;
                        if (toTable.Rows.Count <= j)
                        {
                            object[] vals = new object[toTable.Columns.Count];
                            vals[toTableCol.Ordinal] = putValue;
                            toTableRow = toTable.Rows.Add(vals);
                        }
                        else
                        {
                            toTableRow = toTable.Rows[j];
                            toTableRow[toTableCol] = putValue;
                        }                        
                    }
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// Возвращает число строк в произвольной таблице
        /// </summary>
        public virtual int CustomRowsCount(object tableZone, string tableName)
        {            
            DataTable dt = GetTable(tableZone, tableName);
            return dt.Rows.Count;
        }

        /// <summary>
        /// Возвращает значения всех полей строки по ее индексу в произвольной таблице
        /// </summary>
        /// <param name="needTable"></param>
        /// <param name="knownField"></param>
        /// <returns></returns>
        public INamedList CustomGetRowValuesByIndex(object tableZone, string tableName, int rowIndex)
        {
            DataTable dt = GetTable(tableZone, tableName);
            INamedList res = new NamedList(); 
            object[] haveValues = dt.Rows[rowIndex].ItemArray;
            int c = haveValues.Length;
            for (int i = 0; i < c; i++)
            {
                res.AddValue(dt.Columns[i].ColumnName, haveValues[i]);                
            }
            return res;
        }

        /// <summary>
        /// Меняет местами 2 колонки в произвольной таблице
        /// </summary>
        /// <param name="tableZone"></param>
        /// <param name="tableName"></param>
        /// <param name="col1Name"></param>
        /// <param name="col2Name"></param>
        /// <remarks>подходит только для полей с одинаковым типом</remarks>
        public virtual bool CustomSwapColumnsValuesAndFixRelations(object tableZone, string tableName, string col1Name, string col2Name)
        {
            if (!Locked)
            {
                DataTable dt = GetTable(tableZone, tableName);
                DataColumn col1 = dt.Columns[col1Name];
                DataColumn col2 = dt.Columns[col2Name];
                //ищем связи
                DataRelation relationFromCol1 = null;
                DataRelation relationFromCol2 = null;
                DataRelation relationToCol1 = null;
                DataRelation relationToCol2 = null;                
                DataRelationCollection rels = dt.ChildRelations;
                int c = rels.Count;                
                for (int i = 0; i < c; i++)
                {                    
                    DataRelation rel = rels[i];                    
                    if (rel.ParentColumns[0] == col1)
                    {
                        relationFromCol1 = rel;
                    }
                    if (rel.ParentColumns[0] == col2)
                    {
                        relationFromCol2 = rel;
                    }
                }
                rels = dt.ParentRelations;
                c = rels.Count;
                for (int i = 0; i < c; i++)
                {
                    DataRelation rel = rels[i];
                    if (rel.ChildColumns[0] == col1)
                    {
                        relationToCol1 = rel;
                    }
                    if (rel.ChildColumns[0] == col2)
                    {
                        relationToCol2 = rel;
                    }
                }
                //отцепляем эти связи
                DataTable relationFromCol1_ToTable = null;
                DataColumn relationFromCol1_ToColumn = null;
                if (relationFromCol1 != null)
                {
                    relationFromCol1_ToTable = relationFromCol1.ChildTable;
                    relationFromCol1_ToColumn = relationFromCol1.ChildColumns[0];
                    relationFromCol1_ToTable.Constraints.Remove(relationFromCol1.ParentKeyConstraint);
                }                
                _dset.Relations.Remove(relationFromCol1);
                DataTable relationFromCol2_ToTable = null;
                DataColumn relationFromCol2_ToColumn = null;
                if (relationFromCol2 != null)
                {
                    relationFromCol2_ToTable = relationFromCol2.ChildTable;
                    relationFromCol2_ToColumn = relationFromCol2.ChildColumns[0];
                    relationFromCol2_ToTable.Constraints.Remove(relationFromCol2.ParentKeyConstraint);
                }                
                _dset.Relations.Remove(relationFromCol2);
                DataTable relationToCol1_FromTable = null;
                DataColumn relationToCol1_FromColumn = null;
                if (relationToCol1 != null)
                {
                    relationToCol1_FromTable = relationToCol1.ParentTable;
                    relationToCol1_FromColumn = relationToCol1.ParentColumns[0];
                    dt.Constraints.Remove(relationToCol1.ChildKeyConstraint);
                    //relationToCol1_FromTable.Constraints.Remove(relationToCol1.ChildKeyConstraint);
                }                
                _dset.Relations.Remove(relationToCol1);
                DataTable relationToCol2_FromTable = null;
                DataColumn relationToCol2_FromColumn = null;
                if (relationToCol2 != null)
                {
                    relationToCol2_FromTable = relationToCol2.ParentTable;
                    relationToCol2_FromColumn = relationToCol2.ParentColumns[0];
                    dt.Constraints.Remove(relationToCol2.ChildKeyConstraint);
                    //relationToCol2_FromTable.Constraints.Remove(relationToCol2.ChildKeyConstraint);
                }                
                _dset.Relations.Remove(relationToCol2);
                //меняем значения в колонках
                c = dt.Rows.Count;
                for (int i = 0; i < c; i++)
                {
                    DataRow row = dt.Rows[i];
                    object mem = row[col1];
                    row[col1] = row[col2];
                    row[col2] = mem;
                }
                //подцепляем связи обратно (поменяв колонки)
                if (relationFromCol1_ToTable != null)
                {
                    CustomBindTablesNow(dt, col2.ColumnName, relationFromCol1_ToTable, relationFromCol1_ToColumn.ColumnName);
                }
                if (relationFromCol2_ToTable != null)
                {
                    CustomBindTablesNow(dt, col1.ColumnName, relationFromCol2_ToTable, relationFromCol2_ToColumn.ColumnName);
                }
                if (relationToCol1_FromTable != null)
                {
                    CustomBindTablesNow(relationToCol1_FromTable, relationToCol1_FromColumn.ColumnName, dt, col2.ColumnName);
                }
                if (relationToCol2_FromTable != null)
                {
                    CustomBindTablesNow(relationToCol2_FromTable, relationToCol2_FromColumn.ColumnName, dt, col1.ColumnName);
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// Смещает значения в произвольной таблице влево, начиная с некоторого столбца
        /// </summary>
        /// <param name="startingColumnName">поле, после которого
        /// значения во всех оставшихся полях будут сдвинуты влево
        /// (т.е. значения в данном поле перетрутся)</param>
        /// <param name="findValues">если этот параметр задан, то
        /// смещает только в определенной строке; если же нет -
        /// то во всех строках таблицы (в этом случае тогда еще
        /// и будут скорректированы отношения, ведущие к/из данных
        /// полей</param>
        /// <param name="nullValue">значение, которое будет
        /// подставляться в пустые ячейки</param>
        /// <returns>возвращает список связей идущих во все поля
        /// таблицы</returns>
        /// <remarks>метод подходит лишь в том случае, если
        /// все поля правее заданного имеют тот же тип, что и
        /// заданный</remarks>         
        public virtual INamedList CustomShiftValuesLeft(object tableZone, string tableName, INamedList findValues, string startingColumnName, object nullValue)
        {
            if (!Locked)
            {
                DataTable dt = GetTable(tableZone, tableName);
                DataColumn startingColumn = dt.Columns[startingColumnName];
                List<int> indicies = new List<int>();                
                if (findValues != null)                
                {
                    indicies = CustomGetRowIndiciesByKnownFields(dt, findValues, true);
                }
                else
                {
                    int c=dt.Rows.Count;
                    for (int i=0;i<c;i++)
                    {
                        indicies.Add(i);
                    }
                }
                int rc = indicies.Count;
                for (int ri = 0; ri < rc; ri++)
                {                    
                    object[] objs = dt.Rows[indicies[ri]].ItemArray;
                    int c = objs.Length;
                    for (int i = startingColumn.Ordinal; i < c-2; i++)//-2 потому что последний столбец - Timestamp
                    {
                        DataTable relationFromCol1_ToTable = null;
                        //DataColumn relationFromCol1_ToColumn = null;
                        DataTable relationFromCol2_ToTable = null;
                        DataColumn relationFromCol2_ToColumn = null;
                        DataTable relationToCol1_FromTable = null;
                        //DataColumn relationToCol1_FromColumn = null;
                        DataTable relationToCol2_FromTable = null;
                        DataColumn relationToCol2_FromColumn = null;
                        DataColumn col1 = null;
                        DataColumn col2 = null;
                        if (findValues==null)
                        {
                            col1=dt.Columns[i];
                            col2=dt.Columns[i+1];
                            //ищем связи
                            DataRelation relationFromCol1 = null;
                            DataRelation relationFromCol2 = null;
                            DataRelation relationToCol1 = null;
                            DataRelation relationToCol2 = null;
                            DataRelationCollection rels = dt.ParentRelations;
                            int cc = rels.Count;
                            for (int j = 0; j < cc; j++)
                            {
                                DataRelation rel = rels[j];
                                if (rel.ParentColumns[0] == col1)
                                {
                                    relationFromCol1 = rel;
                                }
                                if (rel.ParentColumns[0] == col2)
                                {
                                    relationFromCol2 = rel;
                                }
                            }
                            rels = dt.ParentRelations;
                            cc = rels.Count;
                            for (int j = 0; j < cc; j++)
                            {
                                DataRelation rel = rels[j];
                                if (rel.ChildColumns[0] == col1)
                                {
                                    relationToCol1 = rel;
                                }
                                if (rel.ChildColumns[0] == col2)
                                {
                                    relationToCol2 = rel;
                                }
                            }
                            //отцепляем эти связи                            
                            if (relationFromCol1 != null)
                            {
                                relationFromCol1_ToTable = relationFromCol1.ChildTable;
                                //relationFromCol1_ToColumn = relationFromCol1.ChildColumns[0];
                            }
                            relationFromCol1_ToTable.Constraints.Remove(relationFromCol1.ChildKeyConstraint);
                            _dset.Relations.Remove(relationFromCol1);                            
                            if (relationFromCol2 != null)
                            {
                                relationFromCol2_ToTable = relationFromCol2.ChildTable;
                                relationFromCol2_ToColumn = relationFromCol2.ChildColumns[0];
                            }
                            relationFromCol2_ToTable.Constraints.Remove(relationFromCol2.ChildKeyConstraint);
                            _dset.Relations.Remove(relationFromCol2);                            
                            if (relationToCol1 != null)
                            {
                                relationToCol1_FromTable = relationToCol1.ParentTable;
                                //relationToCol1_FromColumn = relationToCol1.ParentColumns[0];
                            }
                            relationToCol1_FromTable.Constraints.Remove(relationToCol1.ParentKeyConstraint);
                            _dset.Relations.Remove(relationToCol1);                            
                            if (relationToCol2 != null)
                            {
                                relationToCol2_FromTable = relationToCol2.ParentTable;
                                relationToCol2_FromColumn = relationToCol2.ParentColumns[0];
                            }
                            relationToCol2_FromTable.Constraints.Remove(relationToCol2.ParentKeyConstraint);
                            _dset.Relations.Remove(relationToCol2);
                        }
                        objs[i]=objs[i+1];            
                        dt.Rows[indicies[ri]].ItemArray = objs;
                        if (findValues == null)
                        {
                            //подцепляем связи обратно (поменяв колонки)                            
                            if (relationFromCol2_ToTable != null)
                            {
                                CustomBindTablesNow(dt, col1.ColumnName, relationFromCol2_ToTable, relationFromCol2_ToColumn.ColumnName);
                            }                            
                            if (relationToCol2_FromTable != null)
                            {
                                CustomBindTablesNow(relationToCol2_FromTable, relationToCol2_FromColumn.ColumnName, dt, col1.ColumnName);
                            }
                        }                        
                    }
                    objs[c - 1] = nullValue;
                    dt.Rows[indicies[ri]].ItemArray = objs;
                }
                INamedList relnames = new NamedList();
                DataRelationCollection relats = dt.ParentRelations;
                foreach (DataRelation rel in relats)
                {
                    relnames.AddValue(rel.ChildColumns[0].ColumnName, rel.RelationName);                            
                }
                return relnames;
            }
            Assert.Fail(Assert.Id.Assert00060);
            return null;
        }

        /// <summary>
        /// Возвращает индексы строк по значению известных полей произвольной таблицы
        /// </summary>        
        /// <returns></returns>
        private static List<int> CustomGetRowIndiciesByKnownFields(DataTable dt, INamedList knownFields, bool findAll)
        {            
            List<int> res = new List<int>();
            int c = dt.Rows.Count;
            for (int i = 0; i < c; i++)            
            {
                object[] haveValues = dt.Rows[i].ItemArray;                                
                bool wrongrow = false;
                foreach(KeyValuePair<string,object> entry in knownFields)
                {                    
                    if (!(GeneralMethods.Identical(haveValues[dt.Columns[entry.Key].Ordinal], entry.Value)))
                    {
                        wrongrow = true;
                        break;
                    }
                }
                if (!wrongrow)
                {
                    res.Add(i);
                    if (!findAll)
                    {
                        break;
                    }
                }
            }
            return res;
        }

        /// <summary>
        /// Устанавливает первичный ключ произвольной таблицы
        /// </summary>
        /// <param name="columnsNames"></param>
        public virtual void CustomSetPrimaryFields(object tableZone, string tableName, ICollection<string> columnsNames)
        {
            if (!Locked)
            {                
                DataTable dt = GetTable(tableZone, tableName);
                CustomSetPrimaryFieldsNow(dt, columnsNames);                
            }            
        }

        protected virtual bool CustomSetPrimaryFieldsNow(DataTable dt, ICollection<string> columnsNames)
        {            
            DataColumn[] befPrim = dt.PrimaryKey;
            List<DataColumn> prim = new List<DataColumn>();
            foreach(string cName in columnsNames)
            {
                DataColumn col = dt.Columns[cName];
                Assert.FailIf(col == null, Assert.Id.Assert00061);
                prim.Add(col);
            }
            try
            {
                dt.PrimaryKey = prim.ToArray();
            }
            catch (ArgumentException ex)
            {
                dt.PrimaryKey = befPrim;
                throw new BITablePrimaryException(ex);
            }
            catch (DataException ex)
            {
                dt.PrimaryKey = befPrim;
                throw new BITablePrimaryException(ex);
            }
            return !dt.PrimaryKey.Equals(befPrim);
        }

        /// <summary>
        /// Вставляет строку в произвольную таблицу
        /// </summary>        
        /// <param name="newValues">значения в новой строке</param>        
        public virtual bool CustomInsertRow(object tableZone, string tableName, INamedList newValues, int rowIndex)
        {
            if (!Locked)
            {
                DataTable dt = GetTable(tableZone, tableName);
                object[] objs = new object[dt.Columns.Count];
                foreach (KeyValuePair<string, object> entry in newValues)
                {
                    objs[dt.Columns[entry.Key].Ordinal] = entry.Value;
                }
                while (true)
                {
                    if ((rowIndex >= dt.Rows.Count) || ((rowIndex) < 0 && (dt.Rows.Count == 0)))
                    {
                        dt.Rows.Add(objs);
                        break;
                    }
                    if (rowIndex < 0)
                        rowIndex = 0;                    
                    var row = dt.NewRow();
                    row.ItemArray = objs;
                    dt.Rows.InsertAt(row, rowIndex);
                    break;
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// Добавляет строку в конец произвольной таблицы
        /// </summary>        
        /// <param name="newValues">значения в новой строке</param>        
        public virtual bool CustomAddRow(object tableZone, string tableName, INamedList newValues)
        {               
            DataTable dt = GetTable(tableZone, tableName);
            return CustomInsertRow(tableZone, tableName, newValues, dt.Rows.Count);
        }

        /// <summary>
        /// Возвращает значения всех полей произвольной таблицы первой встретившейся строки, у которой в нужном поле указано искомое значение
        /// </summary>
        /// <param name="needTable"></param>
        /// <param name="knownField"></param>
        /// <returns>значеня полей + в тэге у возвращаемого списка хранится номер строки</returns>
        public INamedList CustomGetFirstRowValuesByKnownFields(object tableZone, string tableName, INamedList knownFields)
        {
            DataTable dt = GetTable(tableZone, tableName);
            INamedList res = new NamedList();
            List<int> indicies = CustomGetRowIndiciesByKnownFields(dt, knownFields, false);
            if (indicies.Count == 0)
            {
                //не нашли такую строку
                res.Tag = -1;
                return res;
            }
            res.Tag = indicies[0];
            object[] haveValues = dt.Rows[indicies[0]].ItemArray;
            int c = haveValues.Length;
            for (int i = 0; i < c; i++)
            {
                res.AddValue(dt.Columns[i].ColumnName, haveValues[i]);
            }
            return res;
        }

        /*оказалось пока не нужно/// <summary>
        /// Возвращает нэймспейс родительской таблицы отношения
        /// </summary>
        /// <param name="relationName"></param>
        /// <returns></returns>
        public string CustomGetRelationParentTableNamespace(string relationName)
        {
            return _dset.Relations[relationName].ParentTable.Namespace;
        }*/

        /// <summary>
        /// Возвращает имя родительской таблицы отношения
        /// </summary>
        /// <param name="relationName"></param>
        /// <returns></returns>
        public string CustomGetRelationParentTableName(string relationName)
        {
            return _dset.Relations[relationName].ParentTable.TableName;
        }

        /// <summary>
        /// Возвращает имя колонки родительской таблицы отношения
        /// </summary>
        /// <param name="relationName"></param>
        /// <returns></returns>
        public string CustomGetRelationParentColumnName(string relationName)
        {
            return _dset.Relations[relationName].ParentColumns[0].ColumnName;
        }

        /*оказалось пока не нужно/// <summary>
        /// Возвращает нэймспейс дочерней таблицы отношения
        /// </summary>
        /// <param name="relationName"></param>
        /// <returns></returns>
        public string CustomGetRelationChildTableNamespace(string relationName)
        {
            return _dset.Relations[relationName].ChildTable.Namespace;
        }*/

        /// <summary>
        /// Возвращает имя дочерней таблицы отношения
        /// </summary>
        /// <param name="relationName"></param>
        /// <returns></returns>
        public string CustomGetRelationChildTableName(string relationName)
        {
            return _dset.Relations[relationName].ChildTable.TableName;
        }

        /// <summary>
        /// Возвращает имя колонки дочерней таблицы отношения
        /// </summary>
        /// <param name="relationName"></param>
        /// <returns></returns>
        public string CustomGetRelationChildColumnName(string relationName)
        {
            return _dset.Relations[relationName].ChildColumns[0].ColumnName;
        }

        /// <summary>
        /// Удаляет строки из произвольной таблицы по известным полям
        /// </summary>        
        /// <param name="needFieldValues"></param>
        public virtual bool CustomDeleteRowsByKnownFields(object tableZone, string tableName, INamedList needFieldValues)
        {
            if (!Locked)
            {
                DataTable dt = GetTable(tableZone, tableName);
                List<int> indicies = CustomGetRowIndiciesByKnownFields(dt, needFieldValues, true);
                int c = indicies.Count;
                for (int i = c-1; i >= 0; i--)
                {
                    dt.Rows.RemoveAt(indicies[i]);
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// Удаляет все строки из произвольной таблицы, кроме строк содержащих известные значения полей
        /// </summary>        
        /// <param name="needFieldValues"></param>
        public virtual bool CustomDeleteRowsByKnownFieldsInverted(object tableZone, string tableName, INamedList needFieldValues)
        {
            if (!Locked)
            {
                DataTable dt = GetTable(tableZone, tableName);
                List<int> indicies = CustomGetRowIndiciesByKnownFields(dt, needFieldValues, true);
                int c = dt.Rows.Count;
                for (int i = c - 1; i >= 0; i--)
                {
                    if (!indicies.Contains(i))
                    {
                        dt.Rows.RemoveAt(i);
                    }
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// Заменяет строки в произвольной таблице
        /// </summary>                
        public virtual bool CustomChangeRowValues(object tableZone, string tableName, INamedList findValues, INamedList setValues, bool findAll)
        {
            if (!Locked)
            {
                DataTable dt = GetTable(tableZone, tableName);
                List<int> indicies = CustomGetRowIndiciesByKnownFields(dt, findValues, findAll);
                int rc = indicies.Count;
                for (int ri = 0; ri < rc; ri++)
                {
                    object[] objs = dt.Rows[indicies[ri]].ItemArray;
                    foreach(KeyValuePair<string,object> entry in setValues)
                    {                        
                        objs[dt.Columns[entry.Key].Ordinal] = entry.Value;
                    }
                    dt.Rows[indicies[ri]].ItemArray = objs;
                }
                return rc > 0;
            }
            return false;
        }

        /// <summary>
        /// Заменяет строку с указанным индексом в произвольной таблице
        /// </summary>                
        public virtual bool CustomChangeRowValuesByRowIndex(object tableZone, string tableName, int rowIndex, INamedList setValues)
        {
            if (!Locked)
            {
                DataTable dt = GetTable(tableZone, tableName);
                object[] objs = dt.Rows[rowIndex].ItemArray;                
                foreach(KeyValuePair<string,object> entry in setValues)
                {                    
                    objs[dt.Columns[entry.Key].Ordinal] = entry.Value;
                }
                dt.Rows[rowIndex].ItemArray = objs;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Удаляет произвольную таблицу и связи ведущие из нее/в нее
        /// </summary>
        /// <param name="tname"></param>
        public virtual bool CustomDeleteTable(object tableZone, string tableName)
        {
            if (!Locked)
            {
                DataTable dt = GetTable(tableZone, tableName);
                //удаляем связи                
                DataRelationCollection rels = dt.ParentRelations;
                int c = rels.Count;
                for (int i = c-1; i >= 0; i--)
                {
                    DataRelation rel = rels[i];
                    if (rel.ChildKeyConstraint != null)
                    {
                        dt.Constraints.Remove(rel.ChildKeyConstraint);
                    }
                    _dset.Relations.Remove(rel);
                }
                rels = dt.ChildRelations;
                c = rels.Count;
                for (int i = c-1; i >= 0; i--)
                {
                    DataRelation rel = rels[i];
                    if (rel.ParentKeyConstraint != null)
                    {
                        dt.Constraints.Remove(rel.ParentKeyConstraint);
                    }
                    _dset.Relations.Remove(rel);
                }
                //удаляем саму таблицу
                _dset.Tables.Remove(dt);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Возвращает имена полей в произвольной таблице
        /// </summary>                
        public ICollection<string> CustomColumnsNames(object tableZone, string tableName)
        {
            DataTable dt = GetTable(tableZone, tableName);
            List<string> res = new List<string>();
            for (int i = 0; i < dt.Columns.Count; i++)
            {
                res.Add(dt.Columns[i].ColumnName);
            }
            return res;                            
        }
        
        /// <summary>
        /// Возвращает название отношения, ведущего к указанной колонке произвольной таблицы
        /// </summary>                
        public string CustomGetNameOfRelationToField(object tableZone, string tableName, string fieldName)
        {
            DataTable dt = GetTable(tableZone, tableName);
            DataRelationCollection rels = dt.ParentRelations;
            foreach (DataRelation rel in rels)
            {
                if (rel.ChildColumns[0].ColumnName == fieldName)
                {
                    return rel.RelationName;
                }
            }
            return string.Empty;
        }

        /// <summary>
        /// Удаляет указанные отношения
        /// </summary>
        /// <param name="relationsNames"></param>
        public virtual bool CustomClearRelations(ICollection<string> relationsNames)
        {
            if (!Locked)
            {
                foreach (string relname in relationsNames)
                {
                    _dset.Relations.Remove(relname);
                }                
                return true;
            }
            return false;
        }

        /// <summary>
        /// Удаляет строку с указанным индексом из произвольной таблицы
        /// </summary>                
        public virtual bool CustomDeleteRowByIndex(object tableZone, string tableName, int rowIndex)
        {
            if (!Locked)
            {
                DataTable dt = GetTable(tableZone, tableName);
                dt.Rows.RemoveAt(rowIndex);
                return true;
            }
            return false;
        }
        #endregion
    }
}
